﻿//**********************************************************//
//  Author:         Eric Chaney                             //
//  Date:           Spring 2010                             //
//  Description:    A sound managing utility designed for   //
//                  easy implementation and use. It handles //
//                  both MP3s and WAV filetypes.            //
//  Version:        1.25                                    //
//**********************************************************//

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Media;

namespace NapazEngine.Utilities.Data
{
    public class SoundManager: GameComponent
    {
        #region Instance Variables
        private static ContentManager content;
        private Dictionary<String, SoundObject> files;

        public const float DEFAULT_VOLUME = SoundObject.DEFAULT_VOLUME;
        public const float DEFAULT_PITCH = SoundObject.DEFAULT_PITCH;
        public const bool DEFAULT_REPEAT = SoundObject.DEFAULT_REPEAT;


        /// <summary>
        /// Small struct to hold settings for each sound
        /// </summary>
        struct SoundObject
        {
            public const float DEFAULT_VOLUME = 1.0f;
            public const float DEFAULT_PITCH = 0.0f;
            public const bool DEFAULT_REPEAT = false;

            public float volume;
            public float pitch;
            public bool repeat;
            public Song song;
            public SoundEffect effect;
            public SoundEffectInstance instance;

            /// <summary>
            /// constructor for SoundEffects (filetype wav)
            /// </summary>
            /// <param name="sound">The sound effect to be played.</param>
            /// <param name="Volume">Volume</param>
            /// <param name="Pitch">Pitch</param>
            /// <param name="Repeat">Repeat</param>
            public SoundObject(SoundEffect effect, SoundEffectInstance sound, float Volume, float Pitch, bool Repeat)
            {
                this.effect = effect;
                instance = sound;
                song = null;
                volume = Volume;
                pitch = Pitch;
                repeat = Repeat;
            }

            /// <summary>
            /// constructor for Songs (filetype mp3)
            /// </summary>
            /// <param name="sound">The song to be played</param>
            /// <param name="Volume">Volume</param>
            /// <param name="Repeat">Repeat</param>
            public SoundObject(Song sound, float Volume, bool Repeat)
            {
                effect = null;
                instance = null;
                song = sound;
                volume = Volume;
                repeat = Repeat;

                //default pitch since MediaPlayer does not support pitch
                pitch = 0.0f;
            }
        }
        #endregion



        #region Constructor
        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="game"></param>
        /// <param name="Content"></param>
        public SoundManager(Game game, ContentManager Content): base(game)
        {
            content = Content;
            files = new Dictionary<String, SoundObject>();
        }
        #endregion



        #region Methods
        /// <summary>
        /// Will attempt to load all MP3s and WAVs in the content project. It uses a structure where all sound effects are in "sound/effects" 
        /// and songs are "sound/songs." This is to remember the distinction between how XNA treats WAVs (SoundEffects) and MP3s (Songs).
        /// </summary>
        /// <param name="filepath"></param>
        public void AddFiles(String filepath)
        {
            DirectoryInfo di = new DirectoryInfo(filepath);

            //load all MP3s
            FileInfo[] rgFiles = di.GetFiles("songs/*.mp3");
            foreach (FileInfo fi in rgFiles)
            {
                AddMP3(filepath, fi.Name, SoundObject.DEFAULT_VOLUME, SoundObject.DEFAULT_REPEAT);
            }

            //load all wavs
            rgFiles = di.GetFiles("effects/*.wav");
            foreach (FileInfo fi in rgFiles)
            {
                AddWAV(filepath, fi.Name, SoundObject.DEFAULT_VOLUME, SoundObject.DEFAULT_PITCH, SoundObject.DEFAULT_REPEAT);
            }
        }

        /// <summary>
        /// The Add method for adding in .wav files
        /// </summary>
        /// <param name="filepath">The path to the folder containing the wav. For example "SoundEffects"</param>
        /// <param name="filename">The name of the wav</param>
        /// <param name="volume">Desired volume</param>
        /// <param name="pitch">Desired pitch, default 0.0f</param>
        /// <param name="repeat">Whether the wav should repeat</param>
        public void AddWAV(String filepath, String filename, float volume, float pitch, bool repeat)
        {
            try
            {
                SoundEffect effect = content.Load<SoundEffect>(filepath + "/" + filename);
                SoundEffectInstance instance = effect.CreateInstance();
                instance.Volume = volume;
                instance.Pitch = pitch;
                instance.IsLooped = repeat;
                files.Add(filename, new SoundObject(effect, instance, volume, pitch, repeat));
            }
            catch (Exception e)
            {
                Console.WriteLine("SoundManager - Could not load WAV: " + filepath + "/" + filename);
                Console.WriteLine(e.Message);
            }
        }


        /// <summary>
        /// The Add method for adding in mp3 files
        /// </summary>
        /// <param name="filepath">The path to the folder containing the mp3. For example "Songs"</param>
        /// <param name="filename">The name of the mp3</param>
        /// <param name="volume">Desired volume</param>
        /// <param name="repeat">Whether the mp3 should repeat</param>
        public void AddMP3(String filepath, String filename, float volume, bool repeat)
        {
            try
            {
                Song song = content.Load<Song>(filepath + "/" + filename);
                files.Add(filename, new SoundObject(song, volume, repeat));
            }
            catch (Exception e)
            {
                Console.WriteLine("SoundManager - Could not load MP3: " + filepath + "/" + filename);
                Console.WriteLine(e.Message);
            }
        }


        /// <summary>
        /// Default method to play sounds/songs. Handles both WAV and MP3
        /// </summary>
        /// <param name="filename">The name of the sound to play</param>
        public void Play(String filename)
        {
            if (files.ContainsKey(filename))
            {
                SoundObject sound = files[filename];

                if (sound.song == null)
                {
                    sound.instance.Play();
                }
                else
                {
                    Song song = sound.song;
                    MediaPlayer.Stop();
                    MediaPlayer.Volume = sound.volume;
                    MediaPlayer.IsRepeating = sound.repeat;
                    MediaPlayer.Play(song);
                }
            }
            else
            {
                Console.WriteLine("ERROR: soundfile " + filename + " does not exist");
            }
        }

        /// <summary>
        /// Plays a new instance 
        /// </summary>
        /// <param name="filename"></param>
        public void PlayInstance(String filename)
        {
            if (files.ContainsKey(filename))
            {
                SoundObject sound = files[filename];
                SoundEffect effect = sound.effect;
                SoundEffectInstance newInstance = effect.CreateInstance();
                newInstance.Volume = sound.volume;
                newInstance.Pitch = sound.pitch;
                newInstance.Play();
            }
            else
            {
                Console.WriteLine("ERROR: soundfile " + filename + " does not exist");
            }
        }

        public void Play3D(String filename, AudioEmitter emitter, AudioListener listener)
        {
            if (files.ContainsKey(filename))
            {
                SoundObject sound = files[filename];
                SoundEffectInstance instance = sound.instance;
                instance.Apply3D(listener, emitter);
                instance.Play();
            }
            else
            {
                Console.WriteLine("ERROR: soundfile " + filename + " does not exist");
            }
        }


        /// <summary>
        /// Default method for stopping playback of a sound
        /// </summary>
        /// <param name="filename">The name of the sound to stop</param>
        public void Stop(String filename)
        {
            if (files.ContainsKey(filename))
            {
                SoundObject sound = files[filename];

                if (sound.song == null)
                {
                    SoundEffectInstance instance = sound.instance;
                    instance.Stop();
                }
                else
                {
                    MediaPlayer.Stop();
                }
            }
        }


        /// <summary>
        /// Modifies the pitch of the file
        /// </summary>
        /// <param name="filename">File to modify</param>
        /// <param name="Pitch">desired float value for WAV</param>
        public void SetPitch(String filename, float Pitch)
        {
            SoundObject sound = files[filename];
            float pitch = Pitch;

            //limit our range
            if (Pitch > 1)
                pitch = 1;
            if (Pitch < -1)
                pitch = -1;

            sound.pitch = Pitch;
            if (sound.song == null)
            {
                sound.instance.Pitch = pitch;
            }
        }


        /// <summary>
        /// Sets the volume of the file
        /// </summary>
        /// <param name="filename">File to modify</param>
        /// <param name="Volume">Desired volume to play at</param>
        public void SetVolume(String filename, float Volume)
        {
            SoundObject sound = files[filename];
            float volume = Volume;

            //limit our range
            if (Volume > 1)
                volume = 1;
            if (Volume < 0)
                volume = 0;

            sound.volume = volume;
            if (sound.song == null)
            {
                sound.instance.Volume = volume;
            }
            else
            {
                MediaPlayer.Volume = volume;
            }
        }


        /// <summary>
        /// This will stop playback of all sounds and songs
        /// </summary>
        public void StopAll()
        {
            MediaPlayer.Stop();
            foreach (KeyValuePair<String, SoundObject> pair in files)
            {
                if (pair.Value.instance != null)
                    pair.Value.instance.Stop();
            }
        }


        /// <summary>
        /// checks whether a given file is playing
        /// </summary>
        /// <param name="name">The name of the sound to check</param>
        /// <returns>boolean true/false</returns>
        public bool isSoundPlaying(String name)
        {
            SoundObject so = files[name];
            if (so.instance != null)
            {
                SoundState state = so.instance.State;
                if (state == SoundState.Playing)
                    return true;
                else
                    return false;
            }
            else
            {
                MediaState state = MediaPlayer.State;
                if (state == MediaState.Playing)
                    return true;
                else
                    return false;
            }
        }
        #endregion
    }
}
